'require network';
'require validation';
'require tools.widgets as widgets';
-'require tools.dnsrecordhandlers as drh';
var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
var callUfpList;
render: function([hosts, duids, pools, networks, macdata]) {
var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
- m, s, o, ss, so, dnss;
+ m, s, o, ss, so;
let noi18nstrings = {
etc_hosts: '<code>/etc/hosts</code>',
s.tab('general', _('General'));
s.tab('devices', _('Devices & Ports'));
- s.tab('dnsrecords', _('DNS Records'));
s.tab('dnssecopt', _('DNSSEC'));
s.tab('filteropts', _('Filter'));
s.tab('forward', _('Forwards'));
so.value('39', _('40: LoongArch 64-bit UEFI boot from HTTP'));
so.value('41', _('41: ARM rpiboot'));
- o = s.taboption('dnsrecords', form.SectionValue, '__dnsrecords__', form.TypedSection, '__dnsrecords__');
-
- dnss = o.subsection;
-
- dnss.anonymous = true;
- dnss.cfgsections = function() { return [ '__dnsrecords__' ] };
-
- dnss.tab('hosts', _('Hostnames'));
- dnss.tab('srvhosts', _('SRV'));
- dnss.tab('mxhosts', _('MX'));
- dnss.tab('cnamehosts', _('CNAME'));
- dnss.tab('dnsrr', _('DNS-RR'));
-
- o = dnss.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
- _('Bind service records to a domain name: specify the location of services. See <a href="%s">RFC2782</a>.').format('https://datatracker.ietf.org/doc/html/rfc2782')
- + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
- + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
- + '<br />' + _('You may add multiple records for the same Target.')
- + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
-
- so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax:') + ' ' + '<code>_service._proto.example.com.</code>');
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = '_sip._tcp.example.com.';
-
- so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'sip.example.com.';
-
- so = ss.option(form.Value, 'port', _('Port'));
- so.rmempty = false;
- so.datatype = 'port';
- so.placeholder = '5060';
-
- so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
- so.rmempty = true;
- so.datatype = 'range(0,65535)';
- so.placeholder = '10';
-
- so = ss.option(form.Value, 'weight', _('Weight'));
- so.rmempty = true;
- so.datatype = 'range(0,65535)';
- so.placeholder = '50';
-
- o = dnss.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
- _('Bind service records to a domain name: specify the location of services.')
- + '<br />' + _('You may add multiple records for the same domain.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
- ss.nodescriptions = true;
-
- so = ss.option(form.Value, 'domain', _('Domain'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'example.com.';
-
- so = ss.option(form.Value, 'relay', _('Relay'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'relay.example.com.';
-
- so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
- so.rmempty = true;
- so.datatype = 'range(0,65535)';
- so.placeholder = '0';
-
- o = dnss.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
- _('Set an alias for a hostname.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
- ss.nodescriptions = true;
-
- so = ss.option(form.Value, 'cname', _('Domain'));
- so.rmempty = false;
- so.validate = validateHostname;
- so.placeholder = 'www.example.com.';
-
- so = ss.option(form.Value, 'target', _('Target'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'example.com.';
-
- o = dnss.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
- _('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
-
- so = ss.option(form.Value, 'name', _('Hostname'));
- so.rmempty = false;
- so.datatype = 'hostname';
-
- so = ss.option(form.Value, 'ip', _('IP address'));
- so.rmempty = false;
- so.datatype = 'ipaddr("nomask")';
-
var ipaddrs = {};
Object.keys(hosts).forEach(function(mac) {
ipaddrs[addrs[i]] = hosts[mac].name || mac;
});
- L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
- so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
- });
-
- o = dnss.taboption('dnsrr', form.SectionValue, '__dnsrr__', form.GridSection, 'dnsrr', null,
- _('Set an arbitrary resource record (RR) type.') + '<br/>' +
- _('Hexdata is automatically en/decoded on save and load'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
- ss.nodescriptions = true;
-
- function hexdecodeload(section_id) {
- let value = uci.get('dhcp', section_id, 'hexdata') || '';
- // Remove any spaces or colons from the hex string - they're allowed
- value = value.replace(/[\s:]/g, '');
- // Hex-decode the string before displaying
- let decodedString = '';
- for (let i = 0; i < value.length; i += 2) {
- decodedString += String.fromCharCode(parseInt(value.substr(i, 2), 16));
- }
- return decodedString;
- }
-
- function hexencodesave(section, value) {
- if (!value || value.length === 0) {
- uci.unset('dhcp', section, 'hexdata');
- return;
- }
- // Hex-encode the string before saving
- const encodedArr = value.split('').map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
- uci.set('dhcp', section, this.option, encodedArr);
- }
-
- so = ss.option(form.Value, 'rrname', _('Resource Record Name'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'svcb.example.com.';
-
- so = ss.option(form.Value, 'rrnumber', _('Resource Record Number'));
- so.rmempty = false;
- so.datatype = 'uinteger';
- so.placeholder = '64';
-
- so = ss.option(form.Value, '_hexdata', _('Raw Data'));
- so.rmempty = true;
- so.datatype = 'string';
- so.placeholder = 'free-form string';
- so.load = hexdecodeload;
- so.write = hexencodesave;
- so.modalonly = true;
- so.depends({ rrnumber: '65', '!reverse': true });
-
- so = ss.option(form.DummyValue, 'hexdata', _('Hex Data'));
- so.width = '50%';
- so.rawhtml = true;
- so.load = function(section_id) {
- let hexdata = uci.get('dhcp', section_id, 'hexdata') || '';
- hexdata = hexdata.replace(/[:]/g, '');
- return hexdata.replace(/(.{2})/g, '$1 ');
- };
-
- function writetype65(section_id, value) {
- let rrnum = uci.get('dhcp', section_id, 'rrnumber');
- if (rrnum !== '65') return;
-
- let priority = parseInt(this.section.formvalue(section_id, '_svc_priority'), 10);
- let target = this.section.formvalue(section_id, '_svc_target') || '.';
- let params = value.trim().split('\n').map(l => l.trim()).filter(Boolean);
-
- const hex = drh.buildSvcbHex(priority, target, params);
- uci.set('dhcp', section_id, 'hexdata', hex);
- };
-
- function loadtype65(section_id) {
- let rrnum = uci.get('dhcp', section_id, 'rrnumber');
- if (rrnum !== '65') return null;
-
- let hexdata = uci.get('dhcp', section_id, 'hexdata');
- return drh.parseSvcbHex(hexdata);
- };
-
- // Type 65 builder fields (hidden unless rrnumber === 65)
- so = ss.option(form.Value, '_svc_priority', _('Svc Priority'));
- so.placeholder = 1;
- so.datatype = 'and(uinteger,min(0),max(65535))'
- so.modalonly = true;
- so.depends({ rrnumber: '65' });
- so.write = writetype65;
- so.load = function(section_id) {
- const parsed = loadtype65(section_id);
- return parsed?.priority?.toString() || '';
- };
-
- so = ss.option(form.Value, '_svc_target', _('Svc Target'));
- so.placeholder = 'svc.example.com.';
- so.dataype = 'hostname';
- so.modalonly = true;
- so.depends({ rrnumber: '65' });
- so.write = writetype65;
- so.load = function(section_id) {
- const parsed = loadtype65(section_id);
- return parsed?.target || '';
- };
-
- so = ss.option(form.TextValue, '_svc_params', _('Svc Parameters'));
- so.placeholder = 'alpn=h2,h3\nipv4hint=192.0.2.1,192.0.2.2\nipv6hint=2001:db8::1,2001:db8::2\nport=8000';
- so.modalonly = true;
- so.rows = 4;
- so.depends({ rrnumber: '65' });
- so.write = writetype65;
- so.load = function(section_id) {
- const parsed = loadtype65(section_id);
- return parsed?.params?.join('\n') || '';
- };
-
o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
_('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.') + '<br />' +
_('The netfilter components below are only regarded when running fw4.'));
o.depends('queryport', '');
// End devices
+ // Begin dnsrecords
+ o = s.taboption('dnsrecords', form.SectionValue, '__dnsrecords__', form.TypedSection, '__dnsrecords__');
+
+ dnss = o.subsection;
+
+ dnss.anonymous = true;
+ dnss.cfgsections = function() { return [ '__dnsrecords__' ] };
+
+ dnss.tab('hosts', _('Hostnames'));
+ dnss.tab('srvhosts', _('SRV'));
+ dnss.tab('mxhosts', _('MX'));
+ dnss.tab('cnamehosts', _('CNAME'));
+ dnss.tab('dnsrr', _('DNS-RR'));
+
+ o = dnss.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
+ _('Bind service records to a domain name: specify the location of services. See <a href="%s">RFC2782</a>.').format('https://datatracker.ietf.org/doc/html/rfc2782')
+ + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
+ + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
+ + '<br />' + _('You may add multiple records for the same Target.')
+ + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+
+ so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax:') + ' ' + '<code>_service._proto.example.com.</code>');
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = '_sip._tcp.example.com.';
+
+ so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'sip.example.com.';
+
+ so = ss.option(form.Value, 'port', _('Port'));
+ so.rmempty = false;
+ so.datatype = 'port';
+ so.placeholder = '5060';
+
+ so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '10';
+
+ so = ss.option(form.Value, 'weight', _('Weight'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '50';
+
+ o = dnss.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
+ _('Bind service records to a domain name: specify the location of services.')
+ + '<br />' + _('You may add multiple records for the same domain.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'domain', _('Domain'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'example.com.';
+
+ so = ss.option(form.Value, 'relay', _('Relay'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'relay.example.com.';
+
+ so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
+ so.rmempty = true;
+ so.datatype = 'range(0,65535)';
+ so.placeholder = '0';
+
+ o = dnss.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
+ _('Set an alias for a hostname.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ so = ss.option(form.Value, 'cname', _('Domain'));
+ so.rmempty = false;
+ so.validate = validateHostname;
+ so.placeholder = 'www.example.com.';
+
+ so = ss.option(form.Value, 'target', _('Target'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'example.com.';
+
+ o = dnss.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
+ _('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+
+ so = ss.option(form.Value, 'name', _('Hostname'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+
+ so = ss.option(form.Value, 'ip', _('IP address'));
+ so.rmempty = false;
+ so.datatype = 'ipaddr("nomask")';
+
+ var ipaddrs = {};
+
+ Object.keys(hosts).forEach(function(mac) {
+ var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
+
+ for (var i = 0; i < addrs.length; i++)
+ ipaddrs[addrs[i]] = hosts[mac].name || mac;
+ });
+
+ L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
+ so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
+ });
+
+ o = dnss.taboption('dnsrr', form.SectionValue, '__dnsrr__', form.GridSection, 'dnsrr', null,
+ _('Set an arbitrary resource record (RR) type.') + '<br/>' +
+ _('Hexdata is automatically en/decoded on save and load'));
+
+ ss = o.subsection;
+
+ ss.addremove = true;
+ ss.anonymous = true;
+ ss.sortable = true;
+ ss.rowcolors = true;
+ ss.nodescriptions = true;
+
+ function hexdecodeload(section_id) {
+ let value = uci.get('dhcp', section_id, 'hexdata') || '';
+ // Remove any spaces or colons from the hex string - they're allowed
+ value = value.replace(/[\s:]/g, '');
+ // Hex-decode the string before displaying
+ let decodedString = '';
+ for (let i = 0; i < value.length; i += 2) {
+ decodedString += String.fromCharCode(parseInt(value.substr(i, 2), 16));
+ }
+ return decodedString;
+ }
+
+ function hexencodesave(section, value) {
+ if (!value || value.length === 0) {
+ uci.unset('dhcp', section, 'hexdata');
+ return;
+ }
+ // Hex-encode the string before saving
+ const encodedArr = value.split('').map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
+ uci.set('dhcp', section, this.option, encodedArr);
+ }
+
+ so = ss.option(form.Value, 'rrname', _('Resource Record Name'));
+ so.rmempty = false;
+ so.datatype = 'hostname';
+ so.placeholder = 'svcb.example.com.';
+
+ so = ss.option(form.Value, 'rrnumber', _('Resource Record Number'));
+ so.rmempty = false;
+ so.datatype = 'uinteger';
+ so.placeholder = '64';
+
+ so = ss.option(form.Value, '_hexdata', _('Raw Data'));
+ so.rmempty = true;
+ so.datatype = 'string';
+ so.placeholder = 'free-form string';
+ so.load = hexdecodeload;
+ so.write = hexencodesave;
+ so.modalonly = true;
+ so.depends({ rrnumber: '65', '!reverse': true });
+
+ so = ss.option(form.DummyValue, 'hexdata', _('Hex Data'));
+ so.width = '50%';
+ so.rawhtml = true;
+ so.load = function(section_id) {
+ let hexdata = uci.get('dhcp', section_id, 'hexdata') || '';
+ hexdata = hexdata.replace(/[:]/g, '');
+ return hexdata.replace(/(.{2})/g, '$1 ');
+ };
+
+ function writetype65(section_id, value) {
+ let rrnum = uci.get('dhcp', section_id, 'rrnumber');
+ if (rrnum !== '65') return;
+
+ let priority = parseInt(this.section.formvalue(section_id, '_svc_priority'), 10);
+ let target = this.section.formvalue(section_id, '_svc_target') || '.';
+ let params = value.trim().split('\n').map(l => l.trim()).filter(Boolean);
+
+ const hex = drh.buildSvcbHex(priority, target, params);
+ uci.set('dhcp', section_id, 'hexdata', hex);
+ };
+
+ function loadtype65(section_id) {
+ let rrnum = uci.get('dhcp', section_id, 'rrnumber');
+ if (rrnum !== '65') return null;
+
+ let hexdata = uci.get('dhcp', section_id, 'hexdata');
+ return drh.parseSvcbHex(hexdata);
+ };
+
+ // Type 65 builder fields (hidden unless rrnumber === 65)
+ so = ss.option(form.Value, '_svc_priority', _('Svc Priority'));
+ so.placeholder = 1;
+ so.datatype = 'and(uinteger,min(0),max(65535))'
+ so.modalonly = true;
+ so.depends({ rrnumber: '65' });
+ so.write = writetype65;
+ so.load = function(section_id) {
+ const parsed = loadtype65(section_id);
+ return parsed?.priority?.toString() || '';
+ };
+
+ so = ss.option(form.Value, '_svc_target', _('Svc Target'));
+ so.placeholder = 'svc.example.com.';
+ so.dataype = 'hostname';
+ so.modalonly = true;
+ so.depends({ rrnumber: '65' });
+ so.write = writetype65;
+ so.load = function(section_id) {
+ const parsed = loadtype65(section_id);
+ return parsed?.target || '';
+ };
+
+ so = ss.option(form.TextValue, '_svc_params', _('Svc Parameters'));
+ so.placeholder = 'alpn=h2,h3\nipv4hint=192.0.2.1,192.0.2.2\nipv6hint=2001:db8::1,2001:db8::2\nport=8000';
+ so.modalonly = true;
+ so.rows = 4;
+ so.depends({ rrnumber: '65' });
+ so.write = writetype65;
+ so.load = function(section_id) {
+ const parsed = loadtype65(section_id);
+ return parsed?.params?.join('\n') || '';
+ };
+ // End dnsrecords
+
s.taboption('filteropts', form.Flag, 'domainneeded',
_('Domain required'),
_('Never forward DNS queries which lack dots or domain parts.') + '<br />' +
so.value('39', _('40: LoongArch 64-bit UEFI boot from HTTP'));
so.value('41', _('41: ARM rpiboot'));
- o = s.taboption('dnsrecords', form.SectionValue, '__dnsrecords__', form.TypedSection, '__dnsrecords__');
-
- dnss = o.subsection;
-
- dnss.anonymous = true;
- dnss.cfgsections = function() { return [ '__dnsrecords__' ] };
-
- dnss.tab('hosts', _('Hostnames'));
- dnss.tab('srvhosts', _('SRV'));
- dnss.tab('mxhosts', _('MX'));
- dnss.tab('cnamehosts', _('CNAME'));
- dnss.tab('dnsrr', _('DNS-RR'));
-
- o = dnss.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
- _('Bind service records to a domain name: specify the location of services. See <a href="%s">RFC2782</a>.').format('https://datatracker.ietf.org/doc/html/rfc2782')
- + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
- + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
- + '<br />' + _('You may add multiple records for the same Target.')
- + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
-
- so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax:') + ' ' + '<code>_service._proto.example.com.</code>');
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = '_sip._tcp.example.com.';
-
- so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'sip.example.com.';
-
- so = ss.option(form.Value, 'port', _('Port'));
- so.rmempty = false;
- so.datatype = 'port';
- so.placeholder = '5060';
-
- so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
- so.rmempty = true;
- so.datatype = 'range(0,65535)';
- so.placeholder = '10';
-
- so = ss.option(form.Value, 'weight', _('Weight'));
- so.rmempty = true;
- so.datatype = 'range(0,65535)';
- so.placeholder = '50';
-
- o = dnss.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
- _('Bind service records to a domain name: specify the location of services.')
- + '<br />' + _('You may add multiple records for the same domain.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
- ss.nodescriptions = true;
-
- so = ss.option(form.Value, 'domain', _('Domain'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'example.com.';
-
- so = ss.option(form.Value, 'relay', _('Relay'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'relay.example.com.';
-
- so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
- so.rmempty = true;
- so.datatype = 'range(0,65535)';
- so.placeholder = '0';
-
- o = dnss.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
- _('Set an alias for a hostname.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
- ss.nodescriptions = true;
-
- so = ss.option(form.Value, 'cname', _('Domain'));
- so.rmempty = false;
- so.validate = validateHostname;
- so.placeholder = 'www.example.com.';
-
- so = ss.option(form.Value, 'target', _('Target'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'example.com.';
-
- o = dnss.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
- _('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
-
- so = ss.option(form.Value, 'name', _('Hostname'));
- so.rmempty = false;
- so.datatype = 'hostname';
-
- so = ss.option(form.Value, 'ip', _('IP address'));
- so.rmempty = false;
- so.datatype = 'ipaddr("nomask")';
-
- var ipaddrs = {};
-
- Object.keys(hosts).forEach(function(mac) {
- var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
-
- for (var i = 0; i < addrs.length; i++)
- ipaddrs[addrs[i]] = hosts[mac].name || mac;
- });
-
- L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
- so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
- });
-
- o = dnss.taboption('dnsrr', form.SectionValue, '__dnsrr__', form.GridSection, 'dnsrr', null,
- _('Set an arbitrary resource record (RR) type.') + '<br/>' +
- _('Hexdata is automatically en/decoded on save and load'));
-
- ss = o.subsection;
-
- ss.addremove = true;
- ss.anonymous = true;
- ss.sortable = true;
- ss.rowcolors = true;
- ss.nodescriptions = true;
-
- function hexdecodeload(section_id) {
- let value = uci.get('dhcp', section_id, 'hexdata') || '';
- // Remove any spaces or colons from the hex string - they're allowed
- value = value.replace(/[\s:]/g, '');
- // Hex-decode the string before displaying
- let decodedString = '';
- for (let i = 0; i < value.length; i += 2) {
- decodedString += String.fromCharCode(parseInt(value.substr(i, 2), 16));
- }
- return decodedString;
- }
-
- function hexencodesave(section, value) {
- if (!value || value.length === 0) {
- uci.unset('dhcp', section, 'hexdata');
- return;
- }
- // Hex-encode the string before saving
- const encodedArr = value.split('').map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join('');
- uci.set('dhcp', section, this.option, encodedArr);
- }
-
- so = ss.option(form.Value, 'rrname', _('Resource Record Name'));
- so.rmempty = false;
- so.datatype = 'hostname';
- so.placeholder = 'svcb.example.com.';
-
- so = ss.option(form.Value, 'rrnumber', _('Resource Record Number'));
- so.rmempty = false;
- so.datatype = 'uinteger';
- so.placeholder = '64';
-
- so = ss.option(form.Value, '_hexdata', _('Raw Data'));
- so.rmempty = true;
- so.datatype = 'string';
- so.placeholder = 'free-form string';
- so.load = hexdecodeload;
- so.write = hexencodesave;
- so.modalonly = true;
- so.depends({ rrnumber: '65', '!reverse': true });
-
- so = ss.option(form.DummyValue, 'hexdata', _('Hex Data'));
- so.width = '50%';
- so.rawhtml = true;
- so.load = function(section_id) {
- let hexdata = uci.get('dhcp', section_id, 'hexdata') || '';
- hexdata = hexdata.replace(/[:]/g, '');
- return hexdata.replace(/(.{2})/g, '$1 ');
- };
-
- function writetype65(section_id, value) {
- let rrnum = uci.get('dhcp', section_id, 'rrnumber');
- if (rrnum !== '65') return;
-
- let priority = parseInt(this.section.formvalue(section_id, '_svc_priority'), 10);
- let target = this.section.formvalue(section_id, '_svc_target') || '.';
- let params = value.trim().split('\n').map(l => l.trim()).filter(Boolean);
-
- const hex = drh.buildSvcbHex(priority, target, params);
- uci.set('dhcp', section_id, 'hexdata', hex);
- };
-
- function loadtype65(section_id) {
- let rrnum = uci.get('dhcp', section_id, 'rrnumber');
- if (rrnum !== '65') return null;
-
- let hexdata = uci.get('dhcp', section_id, 'hexdata');
- return drh.parseSvcbHex(hexdata);
- };
-
- // Type 65 builder fields (hidden unless rrnumber === 65)
- so = ss.option(form.Value, '_svc_priority', _('Svc Priority'));
- so.placeholder = 1;
- so.datatype = 'and(uinteger,min(0),max(65535))'
- so.modalonly = true;
- so.depends({ rrnumber: '65' });
- so.write = writetype65;
- so.load = function(section_id) {
- const parsed = loadtype65(section_id);
- return parsed?.priority?.toString() || '';
- };
-
- so = ss.option(form.Value, '_svc_target', _('Svc Target'));
- so.placeholder = 'svc.example.com.';
- so.dataype = 'hostname';
- so.modalonly = true;
- so.depends({ rrnumber: '65' });
- so.write = writetype65;
- so.load = function(section_id) {
- const parsed = loadtype65(section_id);
- return parsed?.target || '';
- };
-
- so = ss.option(form.TextValue, '_svc_params', _('Svc Parameters'));
- so.placeholder = 'alpn=h2,h3\nipv4hint=192.0.2.1,192.0.2.2\nipv6hint=2001:db8::1,2001:db8::2\nport=8000';
- so.modalonly = true;
- so.rows = 4;
- so.depends({ rrnumber: '65' });
- so.write = writetype65;
- so.load = function(section_id) {
- const parsed = loadtype65(section_id);
- return parsed?.params?.join('\n') || '';
- };
-
o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
_('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.') + '<br />' +
_('The netfilter components below are only regarded when running fw4.'));